package org.xbdframework.boot.autoconfigure.trace;

import java.util.Map;
import java.util.Set;

import org.apache.commons.collections.MapUtils;

import org.xbdframework.trace.interceptor.DefaultTraceAttribute;
import org.xbdframework.trace.interceptor.NameMatchTraceAttributeSource;
import org.xbdframework.trace.interceptor.TraceAttribute;
import org.xbdframework.trace.interceptor.TraceInterceptor;

import org.springframework.aop.Advisor;
import org.springframework.aop.aspectj.AspectJExpressionPointcut;
import org.springframework.aop.support.DefaultPointcutAdvisor;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.AutoConfigureOrder;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.util.CollectionUtils;

/**
 * 系统运行轨迹追踪记录自动化配置.
 * <p>如启用此自动化配置，需实现{@link TracePointcutCustomizer}接口并注册为Bean.
 * {@code Expression}等参数可通过此接口指定.
 * <p>{@code TraceAttributeSource}为{@link NameMatchTraceAttributeSource}.
 * {@code Traceable Method}、{@code TraceAttribute}可通过此接口自定义.
 * @author luas
 * @since 1.5.0
 * @see TracePointcutCustomizer
 * @see NameMatchTraceAttributeSource
 * @see AspectJExpressionPointcut
 * @see DefaultPointcutAdvisor
 */
@Configuration
@ConditionalOnMissingBean(TraceInterceptor.class)
@ConditionalOnBean(TracePointcutCustomizer.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
public class TraceAutoConfiguration {

	private final Set<TraceNameMatchCustomizer> traceNameMatchCustomizers;

	private final TracePointcutCustomizer tracePointcutCustomizer;

	public TraceAutoConfiguration(ObjectProvider<Set<TraceNameMatchCustomizer>> traceNameMatchCustomizerObjectProvider, ObjectProvider<TracePointcutCustomizer> tracePointcutCustomizerObjectProvider) {
		this.traceNameMatchCustomizers = traceNameMatchCustomizerObjectProvider.getIfAvailable();
		this.tracePointcutCustomizer = tracePointcutCustomizerObjectProvider.getIfUnique();
	}

	@Bean
	public TraceInterceptor traceInterceptor() {
		TraceInterceptor interceptor = new TraceInterceptor();

		DefaultTraceAttribute defaultTraceAttribute = new DefaultTraceAttribute();

		NameMatchTraceAttributeSource traceAttributeSource = new NameMatchTraceAttributeSource();
		traceAttributeSource.addTraceableMethod("*", defaultTraceAttribute);

		customize(traceAttributeSource);

		interceptor.setTraceAttributeSource(traceAttributeSource);

		return interceptor;
	}

	@Bean
	public Advisor traceAdvisor() {
		AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();

		customize(pointcut);

		DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor();
		advisor.setPointcut(pointcut);
		advisor.setAdvice(traceInterceptor());

		return advisor;
	}

	private void customize(NameMatchTraceAttributeSource traceAttributeSource) {
		if (CollectionUtils.isEmpty(this.traceNameMatchCustomizers)) {
			return;
		}

		for (TraceNameMatchCustomizer customizer : this.traceNameMatchCustomizers) {
			Map<String, TraceAttribute> attributeMap = customizer.customize();
			if (MapUtils.isNotEmpty(attributeMap)) {
				for (Map.Entry<String, TraceAttribute> entry : attributeMap.entrySet()) {
					traceAttributeSource.addTraceableMethod(entry.getKey(), entry.getValue());
				}
			}
		}
	}

	private void customize(AspectJExpressionPointcut pointcut) {
		this.tracePointcutCustomizer.customize(pointcut);
	}

}
